#include "DaviesMaterial.h"

using namespace OBR;

std::shared_ptr<CudaProgram> DaviesMaterial::program;
CudaBuffer DaviesMaterial::sbt_data_payload;

DaviesMaterial::DaviesMaterial()
    : Material("shaders/Material/DaviesMaterial.cu"), rms(0.02), a(0.85), rho(0.9)
{
    this->program_desc.callables.entryFunctionNameDC = "__direct_callable__davies_material";
    this->program_desc.kind = OPTIX_PROGRAM_GROUP_KIND_CALLABLES;
    this->program_desc.flags = OPTIX_PROGRAM_GROUP_FLAGS_NONE;
}

DaviesMaterial::~DaviesMaterial()
{
}

void DaviesMaterial::uploadVirtualProgramPayload(
    std::vector<std::shared_ptr<VirtualProgramEntry>> instances)
{
    // collect data
    DaviesMaterialData *data = new DaviesMaterialData[instances.size()];
    for (size_t i = 0; i < instances.size(); i++)
    {
        auto mat = std::dynamic_pointer_cast<DaviesMaterial>(instances[i]);
        data[i].rms = this->rms.getDeviceMipmap();
        data[i].a = this->a.getDeviceMipmap();
        data[i].rho = this->rho.getDeviceMipmap();
    }

    // set payload
    size_t payload_size = sizeof(DaviesMaterialData) * instances.size();
    this->sbt_data_payload.resize(payload_size);
    this->sbt_data_payload.upload(data, payload_size);

    delete[] data;
}

void DaviesMaterial::_buildFromConfig(const Json::Value &config)
{
    if (config["type"].asString().compare("davies") != 0)
    {
        std::cerr << "wrong config value is sent to DaviesMaterial::_buildFromConfig" << std::endl;
        std::cerr << "got:" << config << std::endl;
        throw std::runtime_error("invalid parameter");
    }
    // rms
    FloatTexture2D rms_tex(0);
    ConfigHelpers::loadTexture2D<float>(config["rms"], rms_tex);
    this->rms.createFromTexture(rms_tex);
    // alpha
    FloatTexture2D a_tex(0);
    ConfigHelpers::loadTexture2D<float>(config["a"], a_tex);
    this->a.createFromTexture(a_tex);
    // rho
    FloatTexture2D rho_tex(0);
    ConfigHelpers::loadTexture2D<float>(config["rho"], rho_tex);
    this->a.createFromTexture(rho_tex);
}

CUdeviceptr DaviesMaterial::getPayloadDevicePtr()
{
    return DaviesMaterial::sbt_data_payload.d_pointer();
}

void DaviesMaterial::setProgram(std::shared_ptr<CudaProgram> prog)
{
    DaviesMaterial::program = prog;
}

std::shared_ptr<CudaProgram> DaviesMaterial::getProgram()
{
    return DaviesMaterial::program;
}

OptixProgramGroupOptions &DaviesMaterial::getProgramGroupOptions()
{
    return this->program_opt;
}

OptixProgramGroupDesc &DaviesMaterial::getProgramGroupDesc()
{
    this->program_desc.callables.moduleDC = this->cuda_module->optix_module;
    return this->program_desc;
}